# -*- coding: utf-8 -*-

# zeby nie pisac u przed stringami i miec dzielenie tak jak w pythonie 3

"""
Główny moduł programu odpowiedzialny na działanie głównej pętli programu która
zapewnia możliwość prowadzenia symulacji.

B{Moduł zawiera:}
    1. Klasa MainFrame
        - Metoda OnMenuFileExitClick
    2. Klasa Canvas
        - Metoda __init__
        - Metoda OnTool
        - Metoda OnOptions
        - Metoda createRandomBall
        - Metoda createWalls
        - Metoda OnPaint
        - Metoda OnSize
        - Metoda UpdateBuffer
        - Metoda Redraw
        - Metoda playSimulation
        - Metoda pauseSimulation
        - Metoda stopSimulation
        - Metoda bounceBallsAgainstCollidingWalls
        - Metoda calculateBallsVelocitiesAfterCollision
        - Metoda calculateBallPositionAfterTime
        - Metoda calculateBallVelocityAfterTime
        - Metoda simulate
    4. Klasa OptionsDialog
        - Metoda __init__
        - Metoda OnResetToDefaultsClick
        - Metoda OnApplyClick
    5. Klasa PythonDrawApp
        - Metoda OnInit
"""

from __future__ import unicode_literals, division
import wx
from wx import xrc
from math import sqrt, sin, cos, asin, radians, pi, fabs
from random import randint, choice
from copy import deepcopy
from time import clock
from threading import Thread, Timer
from os import system

from Options import Options
from Ball import Ball
from Wall import Wall
from Point import Point
from Vectors import Velocity, Gravity, Vector



class MainFrame(wx.Frame):
    """
    Klasa implementująca zdarzenia głównej formatki aplikacji.
    
    @param wx.Frame: Klasa ramki, po której dziedziczy główne okno aplikacji.
    """
    
    
    def OnMenuFileExitClick(self, Event):
        """
        Metoda zamykająca okienko aplikacji i kończąca działanie programu.
        
        @param Event: Parametr określający obsługiwane zdarzenie.
        """
        
        xrc.XRCCTRL(self, "Canvas").stopSimulation()
        
        dt = 1 / Options.FramesPerSecond
        Timer(dt*1.5, self.Destroy).start()



class Canvas(wx.Panel, Thread):
    """
    Klasa implementująca zdarzenia związane z rysowaniem na płótnie.
    
    @param wx.Panel: Klasa panelu, po którym dziedziczy płótno.
    @param Thread: Klasa wątku, po którym dziedziczy płótno, aby obsłużyć symulację.
    """ 
    
    InitialBallsList = []
    """
    Zmienna określająca początkową listę piłeczek.
    """
    BallsList = []
    """
    Zmienna określająca listę piłeczek.
    """
    LastRefreshTime = clock()
    """
    Zmienna określająca ostatni czas w którym odświeżone zostało rysowanie na
    płótnie.
    """
    SimulationTime = 0.0
    """
    Zmienna określająca czas trwania symulacji.
    """
    
    StopSimulation = True
    """
    Zmienna określająca czy symulacja jest zatrzymana.
        1. B{True} - Symulacja jest zatrzymana.
        2. B{False} - Symulacja nie jest zatrzymana.
    """
    PlaySimulation = False
    """
    Zmienna określająca czy symulacja włączona.
        1. B{True} - Symulacja jest włączona.
        2. B{False} - Symulacja nie jest włączona.
    """
    PauseSimulation = False
    """
    Zmienna określająca czy symulacja jest wstrzymana.
        1. B{True} - Symulacja jest wstrzymana.
        2. B{False} - Symulacja nie jest wstrzymana.
    """
    
    
    def __init__(self, ToolBar, MainFrame):
        """
        Konstruktor tworzy losowy zestaw piłeczek na podstawie opcji symulacji
        wybranych przez użytkownika.
        
        Zachowuje początkowe położenie i prędkości piłeczek tak, aby można było
        powtórzyć tą samą symulację. Uruchamia wątek rysowania piłeczek na
        ekranie i obliczania ich nowego położenia.
        
        Konstruktor tworzy piłeczki tak aby żadna z piłeczek nie nachodziła na
        siebie i żadna piłeczka nie posiadała tego samego punktu początkowego.
        
        @param ToolBar: Parametr określający pasek narzędzi.
        @param MainFrame: Parametr określający główne okno programu.
        """
        
        self.ToolBar = ToolBar
        self.MainFrame = MainFrame
        self.ToolStop = ToolBar.FindById(xrc.XRCID("ToolStop"))
        self.ToolPlay = ToolBar.FindById(xrc.XRCID("ToolPlay"))
        self.ToolPause = ToolBar.FindById(xrc.XRCID("ToolPause"))
        self.ToolOptions = ToolBar.FindById(xrc.XRCID("ToolOptions"))
        self.ToolVelocities = ToolBar.FindById(xrc.XRCID("ToolVelocities"))
        
        Options.PaintVelocities = self.ToolVelocities.IsToggled()
        
        
        self.InitialBallsList = []
        self.BallsList = []
        self.WallsList = []
        
        
        self.UpdateBuffer()
        
        
        # inicjalizacja listy ścian
        self.WallsList = self.createWalls()
        
        
        # inicjalizacja listy piłeczek
        # piłeczki nie mogą zachodzić na siebie
        # środki żadnych dwóch piłeczek nie mogą być w tym samym punkcie
        for i in range(0, Options.NumberOfBalls):
            
            RandomBall = None
            ballIsColliding = True
            
            while ballIsColliding:
                
                RandomBall = self.createRandomBall()
                ballIsColliding = False
                
                for initialBall in self.InitialBallsList:
                    if RandomBall.collidesWith(initialBall):
                        ballIsColliding = True
                        
                for wall in self.WallsList:
                    if RandomBall.collidesWithWall(wall):
                        ballIsColliding = True
            
            self.InitialBallsList.append(RandomBall)
        
        self.BallsList = deepcopy(self.InitialBallsList)
        
        
        self.SimulationTime = 0.0
        
        
        self.Refresh(False)
        self.Update()
    
    
    def OnTool(self, Event):
        """
        Procedura wywoływana w momencie naciśnięcia jednego z przycisków paska
        narzędzi.
        
        Odpowiada za wywoływanie procedur odpowiedzialnych za obsługę
        włączania, wyłączania lub wstrzymywania symulacji.
        
        W przypadku wciśnięcia przycisku B{Play} blokowana jest możliwość
        otwarcia okna z opcjami symulacji oraz można wcisnąć przycisk B{Pause}
        aby wstrzymać symulację. W przypadku wciśnięcia przycisku B{Stop}
        blokowana jest możliwość wciśnięcia przycisku B{Pause}, symulacja
        zostaje zatrzymana oraz umożliwione zostaje przysiśnięcie przycisku
        B{Options} aby otworzyć okno z opcjami symulacji.
        
        @param Event: Parametr określający obsługiwane zdarzenie.
        """
        
        # przycisk pokazujacy/ukrywajacy linie predkosci
        Options.PaintVelocities = self.ToolVelocities.IsToggled()
        
        
        if self.ToolPlay.IsToggled():
            self.ToolBar.EnableTool(self.ToolOptions.Id, False)
            if self.ToolPause.IsToggled():
                self.pauseSimulation()
            elif self.ToolPlay.IsToggled():
                self.playSimulation()
        if self.ToolStop.IsToggled():
            self.ToolBar.ToggleTool(self.ToolPause.GetId(), False)
            self.stopSimulation()
            self.ToolBar.EnableTool(self.ToolOptions.Id, True)
        
        
        # wszystkie pozostałe zdarzenia sa obsługiwane dalej w domyślny sposób
        Event.Skip()
    
    
    def OnOptions(self, Event):
        """
        Procedura wywoływana w momencie naciśnięcia przycisku opcji B{Options}.
        
        @param Event: Parametr określający obsługiwane zdarzenie.
        """

        self.Res = xrc.XmlResource("wxGUI.xrc")
        self.OptionsDialog = self.Res.LoadDialog(self.MainFrame, "OptionsDialog")
        self.OptionsDialog.__class__ = OptionsDialog
        self.OptionsDialog.__init__()
        self.OptionsDialog.Bind(wx.EVT_BUTTON, self.OptionsDialog.OnApplyClick, id = xrc.XRCID("ApplyButton"))
        self.OptionsDialog.Bind(wx.EVT_BUTTON, self.OptionsDialog.OnResetToDefaultsClick, id = xrc.XRCID("ResetToDefaultsButton"))

        # wyświetlenie okienka opcji i inicjalizacja płótna po wyjściu z
        # okienka po zatwierdzeniu nowych wartości
        if self.OptionsDialog.ShowModal() != wx.ID_CANCEL:
            self.__init__(self.ToolBar, self.MainFrame)
    
    
    def createRandomBall(self):
        """
        Funkcja tworząca piłeczkę o losowym położeniu początkowym, promieniu,
        masie i początkowej prędkości zgodnie z minimalnymi i maksymalnymi
        wartościami tych parametrów podanymi w opcjach symulacji.
        
        @return: Funkcja zwraca losowo utworzoną piłeczkę posiadającą określone 
            parametry takie jak:
                1. Promień.
                2. Pozycja na płótnie.
                3. Masa.
                4. Prędkość początkowa (losowa).
        """
        
        CanvasSizeX = self.GetSize().width
        CanvasSizeY = self.GetSize().height
        
        Radius = randint(Options.BallRadiusMin, Options.BallRadiusMax)

        PositionX = randint(0+Radius, CanvasSizeX-Radius)
        PositionY = randint(0+Radius, CanvasSizeY-Radius)
        Position = Point(PositionX, PositionY)

        Mass = randint(Options.BallMassMin, Options.BallMassMax)
        
        
        RandomVelocity = Velocity(0, randint(Options.BallVelocityMin, Options.BallVelocityMax))
        RandomVelocity.rotateVector(randint(0, 360))
        
        
        return Ball(Radius, Position, Mass, RandomVelocity)
    
    
    def createWalls(self):
        """
        Funkcja tworząca predefiniowaną listę ścian - nieruchomych przeszkód
        dla piłeczek.
        
        @return: Funkcja zwraca listę 4 ścian.
        """
        
        CanvasSizeX = self.GetSize().width
        CanvasSizeY = self.GetSize().height
        
        WallsList = []
        
        # dolna ściana
        WallsList.append(Wall(Point(0, 3), Point(CanvasSizeX, 3)))
        # lewa ściana
        WallsList.append(Wall(Point(0, 0), Point(0, CanvasSizeY)))
        # prawa ściana
        WallsList.append(Wall(Point(CanvasSizeX-3, 0), Point(CanvasSizeX-3, CanvasSizeY)))
        # górna ściana
        WallsList.append(Wall(Point(0, CanvasSizeY), Point(CanvasSizeX, CanvasSizeY)))
        
        return WallsList
    
    
    def OnPaint(self, Event):
        """
        Procedura obsługująca zdarzenie aktualizacji zawartości okna, w tym
        położenie piłeczek na płótnie.
        
        @param Event: Parametr określający obsługiwane zdarzenie.      
        """
        
        self.Redraw()
        wx.GCDC(wx.BufferedPaintDC(self, self.Buffer))
    
    
    def OnSize(self, Event):
        """
        Procedura obsługująca zdarzenie zmiany wielkości okna.
        
        @param Event: Parametr określający obsługiwane zdarzenie.
        """
        
        self.UpdateBuffer()
        self.Refresh(False)
    
    
    def UpdateBuffer(self):
        """
        Procedura inicjalizująca płótno programu nową pustą bitmapą.
        """
        
        self.Buffer = wx.EmptyBitmap(max(1,self.GetSize().width), max(1,self.GetSize().height))
    
    
    def Redraw(self):
        """
        Procedura rysująca na płótnie wszystkie piłeczki oraz ściany w ich
        aktualnym położeniu.
        
        Ściany i piłeczki rysowane są za pomocą współrzędnych zawartych w
        odpowiednich listach.
        
        Funkcja odpowiada również za wyświetlanie czasu trwania symulacji.
        """
        
        dc = wx.GCDC(wx.MemoryDC(self.Buffer))
        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
        dc.Clear()
        
        # ustawienie czasu trwania symulacji
        hours = int(self.SimulationTime/360)
        minutes = int((self.SimulationTime - (hours*360))/60)
        seconds = int(self.SimulationTime - (hours*360) - (minutes*60))
        miliseconds = int((self.SimulationTime - (hours*360) - (minutes*60) - (seconds)) * 1000)
        App.StatusBar.SetStatusText("{0:>02d}:{1:>02d}:{2:>02d}.{3:>03d}".format(hours, minutes, seconds, miliseconds), 1)
        
        
        for wall in self.WallsList:
            # uwzględnienie różnic w przyjętych początkach układów
            # współrzędnych wykorzystanych do obliczeń i do rysowania ściany
            # na ekranie
            CanvasPositionX1 = wall.getPoint1().getPositionX()
            CanvasPositionY1 = fabs(wall.getPoint1().getPositionY() - self.GetSize().height)
            CanvasPoint1 = Point(CanvasPositionX1, CanvasPositionY1)
            
            CanvasPositionX2 = wall.getPoint2().getPositionX()
            CanvasPositionY2 = fabs(wall.getPoint2().getPositionY() - self.GetSize().height)
            CanvasPoint2 = Point(CanvasPositionX2, CanvasPositionY2)
            
            wall.draw(dc, CanvasPoint1, CanvasPoint2)
        
        for ball in self.BallsList:
            # uwzględnienie różnic w przyjętych początkach układów
            # współrzędnych wykorzystanych do obliczeń i do rysowania piłeczki
            # na ekranie
            CanvasPositionX = ball.getPosition().getPositionX()
            CanvasPositionY = fabs(ball.getPosition().getPositionY() - self.GetSize().height)
            
            ball.draw(dc, Point(CanvasPositionX, CanvasPositionY), Options.PaintVelocities)
            
        
        # na koniec skasowanie kontekstu - wymagane przez wx
        del(dc)
    
    
    def playSimulation(self):
        """
        Procedura uruchamiająca wątek symulacji.
        
        Jeżeli wciśnięty został przycisk B{Play} to zmienna StopSimulation
        ustawiona zostaje na B{False} a zmienna PlaySimulation na B{True}.
        Gdy w czasie kiedy wciśnięty został przycisk B{Play} symulacja była
        wstrzymana to następuje wznowienie symulacji (ustawienie zmiennej
        PauseSimulation na B{False}).
        """
        
        self.StopSimulation = False
        
        if self.PauseSimulation:
            self.PauseSimulation = False
        
        if not self.PlaySimulation:
            self.PlaySimulation = True
            self.simulate()
    
    
    def pauseSimulation(self):
        """
        Procedura wstrzymująca wątek symulacji.
        """
        
        self.PauseSimulation = True
    
    
    def stopSimulation(self):
        """
        Procedura zatrzymująca wątek symulacji.
        
        Procedura resetuje aktualne położenie piłeczek do położenia przed
        symulacją i resetuje licznik czasu trwania sytuacji.
        """
        
        self.StopSimulation = True
        self.PlaySimulation = False
        self.PauseSimulation = False
        self.BallsList = deepcopy(self.InitialBallsList)
        self.SimulationTime = 0.0
        self.Refresh(False)
        self.Update()
    
    
    #def bounceBallAgainstCollidingWalls2(self, ball):
    #    """
    #    Procedura wyliczająca prędkość piłeczki po odbiciu od ścian, z którymi
    #    styka się w danym momencie piłeczka. Przestrzeń ograniczająca piłeczkę
    #    podana jest w parametrach.
    #    """
    #    
    #    newVelocity = Velocity()
    #    numberOfCollisions = 0
    #    
    #    for wall in self.WallsList:
    #        
    #        if ball.collidesWithWall(wall):
    #            
    #            numberOfCollisions = numberOfCollisions + 1
    #            
    #            a = wall.getPoint1()
    #            b = wall.getPoint2()
    #            
    #            c = deepcopy(a)
    #            ac = Vector(-ball.getVelocity().getVx(), -ball.getVelocity().getVy())
    #            c.moveByVector(ac)
    #            
    #            d = deepcopy(a)
    #            d.moveByVector(ball.getVelocity())
    #            
    #            ab = Vector(b.getPositionX()-a.getPositionX(), b.getPositionY()-a.getPositionY())
    #            
    #            an1 = Vector(-ab.getVy(), ab.getVx())
    #            an1.multiplyVector(1/an1.getVectorLength())
    #            n1 = deepcopy(a)
    #            n1.moveByVector(an1)
    #            dcn1 = Vector(n1.getPositionX()-c.getPositionX(), n1.getPositionY()-c.getPositionY()).getVectorLength()
    #            
    #            an2 = Vector(ab.getVy(), -ab.getVx())
    #            an2.multiplyVector(1/an2.getVectorLength())
    #            n2 = deepcopy(a)
    #            n2.moveByVector(an2)
    #            dcn2 = Vector(n2.getPositionX()-c.getPositionX(), n2.getPositionY()-c.getPositionY()).getVectorLength()
    #            
    #            # jeśli piłeczka nie leci równolegle do ściany
    #            if dcn1 != dcn2:
    #                if dcn1 < dcn2:
    #                    an = an1
    #                else:
    #                    an = an2
    #                
    #                dae = fabs(c.getDistanceToLine(a,b))
    #                
    #                df = deepcopy(an)
    #                df.multiplyVector(2*dae)
    #                
    #                f = deepcopy(d)
    #                f.moveByVector(df)
    #                
    #                af = Vector(f.getPositionX()-a.getPositionX(), f.getPositionY()-a.getPositionY())
    #                
    #                newVelocity.addVector(af)
    #    
    #    
    #    if numberOfCollisions>0:
    #        
    #        if newVelocity==ball.getVelocity() or newVelocity.getVectorLength()==0:
    #            ball.setVelocity(Velocity(-ball.getVelocity().getVx(), -ball.getVelocity().getVy()))
    #        else:
    #            newVelocity.multiplyVector(ball.getVelocity().getVectorLength()/newVelocity.getVectorLength())
    #            ball.setVelocity(newVelocity)
    
    
    def bounceBallsAgainstCollidingWalls(self):
        """
        Procedura wyliczająca prędkość piłeczek po odbiciu od ścian, z którymi
        stykają się w danym momencie piłeczki.
        """
        
        XMin = 0
        XMax = self.GetSize().width
        YMin = 0
        YMax = self.GetSize().height
        
        k = Options.CoefficientOfRestitution
        
        for ball in self.BallsList:
            # wyliczenie prędkości piłeczki po zderzeniu z lewą ścianą
            if (ball.getPosition().getPositionX() - ball.getRadius()) <= XMin and ball.getVelocity().getVx() < 0:
                NewVx = -ball.getVelocity().getVx()
                ball.getVelocity().setVx(NewVx*k)
                ball.setPositionX(XMin + ball.getRadius())
            # wyliczenie prędkości piłeczki po zderzeniu z prawą ścianą
            if (ball.getPosition().getPositionX() + ball.getRadius()) >= XMax and ball.getVelocity().getVx() > 0:
                NewVx = -ball.getVelocity().getVx()
                ball.getVelocity().setVx(NewVx*k)
                ball.setPositionX(XMax - ball.getRadius())
            # wyliczenie prędkości piłeczki po zderzeniu z górną ścianą
            if (ball.getPosition().getPositionY() + ball.getRadius()) >= YMax and ball.getVelocity().getVy() > 0:
                NewVy = -ball.getVelocity().getVy()
                ball.getVelocity().setVy(NewVy*k)
                ball.setPositionY(YMax - ball.getRadius())
            # wyliczenie prędkości piłeczki po zderzeniu z dolną ścianą
            if (ball.getPosition().getPositionY() - ball.getRadius()) <= YMin and ball.getVelocity().getVy() < 0:
                NewVy = -ball.getVelocity().getVy()
                ball.getVelocity().setVy(NewVy*k)
                ball.setPositionY(YMin + ball.getRadius())
    
    
    #def bounceBallAgainstCollidingWalls3(self, ball):
    #    """
    #    Procedura wyliczająca prędkość piłeczki po odbiciu od ścian, z którymi
    #    styka się w danym momencie piłeczka. Przestrzeń ograniczająca piłeczkę
    #    podana jest w parametrach.
    #    """
    #    
    #    # TODO: obliczenie polegające na obliczniu kąta, a nie prostej zmianie
    #    # kierunku - mogą być skośne ściany
    #    
    #    
    #    for wall in self.WallsList:
    #        
    #        if ball.collidesWithWall(wall):
    #            
    #            
    #            XMin = min(wall.getPoint1().getPositionX(), wall.getPoint2().getPositionX())
    #            XMax = max(wall.getPoint1().getPositionX(), wall.getPoint2().getPositionX())
    #            YMin = min(wall.getPoint1().getPositionY(), wall.getPoint2().getPositionY())
    #            YMax = max(wall.getPoint1().getPositionY(), wall.getPoint2().getPositionY())
    #            
    #            
    #            mDenominator = wall.getPoint2().getPositionX() - wall.getPoint1().getPositionX()
    #            
    #            # prosta "pionowa"
    #            if mDenominator==0:
    #                # wyliczenie prędkości piłeczki po zderzeniu z lewą ścianą
    #                if (ball.getPosition().getPositionX() - ball.getRadius()) <= XMin and ball.getVelocity().getVx() < 0:
    #                    NewVx = -ball.getVelocity().getVx()
    #                    ball.getVelocity().setVx(NewVx)
    #                    ball.setPositionX(XMin + ball.getRadius())
    #                # wyliczenie prędkości piłeczki po zderzeniu z prawą ścianą
    #                elif (ball.getPosition().getPositionX() + ball.getRadius()) >= XMax and ball.getVelocity().getVx() > 0:
    #                    NewVx = -ball.getVelocity().getVx()
    #                    ball.getVelocity().setVx(NewVx)
    #                    ball.setPositionX(XMax - ball.getRadius())
    #            else:
    #                # (1)
    #                # (r): y = a*x + b, gdzie b=0
    #                a = (wall.getPoint2().getPositionY()-wall.getPoint1().getPositionY())/mDenominator
    #                
    #                if a==0:
    #                    # prosta "pozioma"
    #                    # wyliczenie prędkości piłeczki po zderzeniu z górną ścianą
    #                    if (ball.getPosition().getPositionY() + ball.getRadius()) >= YMax and ball.getVelocity().getVy() > 0:
    #                        NewVy = -ball.getVelocity().getVy()
    #                        ball.getVelocity().setVy(NewVy)
    #                        ball.setPositionY(YMax - ball.getRadius())
    #                    # wyliczenie prędkości piłeczki po zderzeniu z dolną ścianą
    #                    elif (ball.getPosition().getPositionY() - ball.getRadius()) <= YMin and ball.getVelocity().getVy() < 0:
    #                        NewVy = -ball.getVelocity().getVy()
    #                        ball.getVelocity().setVy(NewVy)
    #                        ball.setPositionY(YMin + ball.getRadius())
    #                else:
    #                    # prosta "ukośna"
    #                    # (2)
    #                    # (p): y = c*x + b, gdzie c=-1/a
    #                    b = ball.getVelocity().getVy() + 1/a*ball.getVelocity().getVx()
    #                    
    #                    # (3)
    #                    Px = b/(a + 1/a)
    #                    Py = a * Px
    #                    
    #                    # (4), (5)
    #                    endVx = (Px*2) - ball.getVelocity().getVx()
    #                    endVy = (Py*2) - ball.getVelocity().getVy()
    #                    
    #                    ball.setVelocity(Velocity(endVx, endVy))
    
    
    def calculateBallsVelocitiesAfterCollision(self):
        """
        Procedura obliczająca nowe prędkości piłeczek po zderzeniu.
        """
        
        # zmienna przechowujaca tymczasowe wyliczone wektory dla kazdej z
        # pileczek po odbiciu z innymi pileczkami. Jesli dana pileczka ma
        # kolizje jednoczesnie z trzema innymi pileczkami, to utworzone zostana
        # dla niej trzy wektory tymczasowe, ktore po zsumowaniu powinny dac
        # wektor odbicia. Ta zmienna ma zawsze dlugosc rowna dlugosci listy
        # pileczek, a kazdej z pileczek przypisane jest 0-n wektorow
        # tymczasowych
        newBallVelocities = []
        
        for i in range(0, len(self.BallsList)):
            newBallVelocities.append([])

        
        k = Options.CoefficientOfRestitution
        
        
        for i in range(0, len(self.BallsList)):
            FirstBall = self.BallsList[i]
            
            for j in range(i+1, len(self.BallsList)):
                SecondBall = self.BallsList[j]
                
                if FirstBall.collidesWith(SecondBall):
                    
                    # get the mtd
                    #
                    delta = Velocity(FirstBall.getPosition().getPositionX() - SecondBall.getPosition().getPositionX(), FirstBall.getPosition().getPositionY() - SecondBall.getPosition().getPositionY())
                    r = FirstBall.getRadius() + SecondBall.getRadius()
                    dist2 = delta.dot(delta)
                    
                    if dist2 > r*r:
                        continue # they aren't colliding
                    
                    
                    
                    d = delta.getVectorLength()
                    
                    mtd = Velocity()
                    
                    if d != 0.0:
                        tempVector = deepcopy(delta)
                        tempVector.multiplyVector(((FirstBall.getRadius() + SecondBall.getRadius())-d)/d) # minimum translation distance to push balls apart after intersecting
                        mtd = deepcopy(tempVector)
                    else: # Special case. Balls are exactly on top of eachother.  Don't want to divide by zero.
                        d = SecondBall.getRadius() + FirstBall.getRadius() - 1.0
                        delta = Velocity(SecondBall.getRadius() + FirstBall.getRadius(), 0.0)
                        tempVector = deepcopy(delta)
                        tempVector.multiplyVector(((FirstBall.getRadius() + SecondBall.getRadius())-d)/d)
                        mtd = deepcopy(tempVector)
            
                    # resolve intersection
                    im1 = 1 / FirstBall.getMass() # inverse mass quantities
                    im2 = 1 / SecondBall.getMass()
             
                    # push-pull them apart
                    tempVector = deepcopy(mtd)
                    tempVector.multiplyVector(im1 / (im1 + im2))
                    FirstBall.getPosition().moveByVector(tempVector)
                    tempVector = deepcopy(mtd)
                    tempVector.multiplyVector(im2 / (im1 + im2))
                    SecondBall.getPosition().moveAgainstVector(tempVector)
             
                    # impact speed
                    tempVector = deepcopy(FirstBall.getVelocity())
                    tempVector.substractVector(SecondBall.getVelocity())
                    v = deepcopy(tempVector)
                    vn = v.dot(mtd.normalize())
             
                    # sphere intersecting but moving away from each other already
                    if (vn > 0.0):
                        continue
                    
                    
                    # cofniecie pileczek jesli na siebie nachodza
                    # TODO: co jesli predkosci sa rowne zero?
                    #BallsAreIntersecting = False
                    #delta = v1n.getVectorLength()
                    #s = (r1+r2) - delta
                    #if s>0:
                    #    BallsAreIntersecting = True
                    #    v = v1.getVectorLength() + v2.getVectorLength()
                    #    s1 = v1.getVectorLength()/v * s
                    #    s2 = v2.getVectorLength()/v * s
                    #    v1s1 = deepcopy(v1)
                    #    v1s1.multiplyVector(s1/v1.getVectorLength())
                    #    FirstBall.getPosition().moveAgainstVector(v1s1)
                    #    v2s2 = deepcopy(v2)
                    #    v2s2.multiplyVector(s2/v2.getVectorLength())
                    #    SecondBall.getPosition().moveAgainstVector(v2s2)
                    m1 = FirstBall.getMass()
                    m2 = SecondBall.getMass()
                    v1 = FirstBall.getVelocity()
                    v2 = SecondBall.getVelocity()
                    r1 = FirstBall.getRadius()
                    r2 = SecondBall.getRadius()
                    
                    ###########################################################
                    #                    SPOSOB Z WIKI                        #
                    #                       POCZATEK                          #
                    ###########################################################
                    
#                    contactAngle = V1.calculateAngleBetweenVector(V2)
#        
#        
#                    # dla przypadku jednowymiarowego - zderzenia centralne
#                    NewVx1 = (Vx1 * (m1-m2) + 2 * m2*Vx2) / (m1 + m2)
#                    NewVy1 = (Vy1 * (m1-m2) + 2 * m2*Vy2) / (m1 + m2)
#                    W1 = Velocity(NewVx1, NewVy1)
#                    #
#                    NewVx2 = (Vx2 * (m2-m1) + 2 * m1*Vx1) / (m1 + m2)
#                    NewVy2 = (Vy2 * (m2-m1) + 2 * m1*Vy1) / (m1 + m2)
#                    W2 = Velocity(NewVx2, NewVy2)
#                    
#                    
#                    # dla przypadku dwuwymiarowego - zderzenia niecentralne, cos nie dziala
#                    #NewVx1 = ((V1Length * cos(V1Angle-contactAngle) * (m1-m2) + 2*m2*V2Length*cos(V2Angle-contactAngle))/(m2+m1))*cos(contactAngle)+V1Length*sin(V1Angle-contactAngle)*cos(contactAngle+radians(pi/2))
#                    #NewVy1 = ((V1Length * cos(V1Angle-contactAngle) * (m1-m2) + 2*m2*V2Length*cos(V2Angle-contactAngle))/(m1+m2))*sin(contactAngle)+V1Length*sin(V1Angle-contactAngle)*sin(contactAngle+radians(pi/2))
#                    #W1 = Velocity(NewVx1, NewVy1)
#                    #
#                    #NewVx2 = ((V2Length * cos(V2Angle-contactAngle) * (m2-m1) + 2*m1*V1Length*cos(V1Angle-contactAngle))/(m2+m1))*cos(contactAngle)+V2Length*sin(V2Angle-contactAngle)*cos(contactAngle+radians(pi/2))
#                    #NewVy2 = ((V2Length * cos(V2Angle-contactAngle) * (m2-m1) + 2*m1*V1Length*cos(V1Angle-contactAngle))/(m1+m2))*sin(contactAngle)+V2Length*sin(V2Angle-contactAngle)*sin(contactAngle+radians(pi/2))
#                    #W2 = Velocity(NewVx2, NewVy2)
                    
                    ###########################################################
                    #                    SPOSOB Z WIKI                        #
                    #                        KONIEC                           #
                    ###########################################################
                    
                    
                    ###########################################################
                    #                    SPOSOB KACPRA                        #
                    #                       POCZATEK                          #
                    ###########################################################
                    
#                    Vx1 = v1.getVx()
#                    Vy1 = v1.getVy()
#                    position1 = FirstBall.getPosition()
#                    theta1 = v1.getVectorAngle()
#                    V1Length = v1.getVectorLength()
#                    
#                    Vx2 = v2.getVx()
#                    Vy2 = v2.getVy()
#                    position2 = SecondBall.getPosition()
#                    theta2 = v2.getVectorAngle()
#                    V2Length = v2.getVectorLength()
#                    
#                    m = m1/m2
#                    
#                    Vr2r1 = Vector(position1.getPositionX()-position2.getPositionX(), position1.getPositionY()-position2.getPositionY())
#                    
#                    #print("ball1mass = {0}, ball2mass = {1}".format(FirstBall.getMass(), SecondBall.getMass()))
#                    # TODO: tu moze byc blad!
#                    phi = Vr2r1.getVectorAngle() - radians(90)
#                    
#                    # sinusy i cosinusy roznic katow theta1-phi oraz theta2-phi
#                    sin_theta1_phi = sin(theta1) * cos(phi) - sin(phi) * cos(theta1)
#                    cos_theta1_phi = sin(theta1) * sin(phi) + cos(phi) * cos(theta1)
#                    sin_theta2_phi = sin(theta2) * cos(phi) - sin(phi) * cos(theta2)
#                    cos_theta2_phi = sin(theta2) * sin(phi) + cos(phi) * cos(theta2)
#                    
#                    # skladowe predkosci kul po zderzeniu w ukladzie wspolrzednych zeta, eta
#                    W_1_zeta = V1Length * cos_theta1_phi
#                    W_1_eta = V1Length * ((m-k)/(m+1)) * sin_theta1_phi + V2Length * ((k+1)/(m+1)) * sin_theta2_phi
#                    
#                    W_2_zeta = V2Length * cos_theta2_phi
#                    W_2_eta = V1Length * (m*(k+1)/(m+1)) * sin_theta1_phi + V2Length * ((1-k*m)/(m+1)) * sin_theta2_phi
#
#                    # skladowe predkosci kul po zderzeniu w ukladzie wspolrzednych x, y
#                    W1x = W_1_zeta * cos(phi) - W_1_eta * sin(phi)
#                    W1y = W_1_zeta * sin(phi) + W_1_eta * cos(phi)
#                    W1 = Vector(W1x, W1y)
#                    
#                    W2x = W_2_zeta * cos(phi) - W_2_eta * sin(phi)
#                    W2y = W_2_zeta * sin(phi) + W_2_eta * cos(phi)
#                    W2 = Vector(W2x, W2y)
                    
                    ###########################################################
                    #                    SPOSOB                               #
                    #                        KONIEC                           #
                    ###########################################################
                    
                    
                    ###########################################################
                    #                       SPOSOB 1                          #
                    #                       POCZATEK                          #
                    ###########################################################
                    #       z ksiazki od Kacpra, wzory 19.15, str. 405        #
                    ###########################################################
                    
#                    # wektory w kierunku normalnym zderzenia: v1n, v2n
#                    v11 = Point(FirstBall.getVelocity().getVx(), FirstBall.getVelocity().getVy())
#                    v1n = Velocity(SecondBall.getPosition().getPositionX()-FirstBall.getPosition().getPositionX(), SecondBall.getPosition().getPositionY()-FirstBall.getPosition().getPositionY())
#                    v1n.multiplyVector(1.0/v1n.getVectorLength())
#                    v1nLength = abs(v11.getDistanceToLine(Point(0,0), Point(v1n.getVx(), v1n.getVy())))
#                    v1n.multiplyVector(v1nLength)
#                    #
#                    v21 = Point(SecondBall.getVelocity().getVx(), SecondBall.getVelocity().getVy())
#                    v2n = Velocity(FirstBall.getPosition().getPositionX()-SecondBall.getPosition().getPositionX(), FirstBall.getPosition().getPositionY()-SecondBall.getPosition().getPositionY())
#                    v2n.multiplyVector(1.0/v2n.getVectorLength())
#                    v2nLength = abs(v21.getDistanceToLine(Point(0,0), Point(v2n.getVx(), v2n.getVy())))
#                    v2n.multiplyVector(v2nLength)
#                    
#                    # wektory w kierunku stycznym zderzenia: v1t, v2t
#                    v1t = deepcopy(v1)
#                    v1t.substractVector(v1n)
#                    v2t = deepcopy(v2)
#                    v2t.substractVector(v2n)
#                    
#                    # wektory w kierunku normalnym po zderzeniu: W1n, W2n
#                    a = (m1 - m2*k)/(m1+m2)
#                    b = (m2 * (k+1))/(m1+m2)
#                    v1na = deepcopy(v1n)
#                    v1na.multiplyVector(a)
#                    v2nb = deepcopy(v2n)
#                    v2nb.multiplyVector(b)
#                    W1n = deepcopy(v1na)
#                    W1n.addVector(v2nb)
#                    #
#                    a = (m1 * (k+1))/(m1+m2)
#                    b = (m2 - m1*k)/(m1+m2)
#                    v1na = deepcopy(v1n)
#                    v1na.multiplyVector(a)
#                    v2nb = deepcopy(v2n)
#                    v2nb.multiplyVector(b)
#                    W2n = deepcopy(v1na)
#                    W2n.addVector(v2nb)
#                    
#                    # wektory po zderzeniu: W1, W2
#                    W1 = deepcopy(W1n)
#                    W1.addVector(v1t)
#                    W2 = deepcopy(W2n)
#                    W2.addVector(v2t)
                    
                    ###########################################################
                    #                       SPOSOB 1                          #
                    #                        KONIEC                           #
                    ###########################################################
                    
                    
                    ###########################################################
                    #                       SPOSOB 2                          #
                    #                       POCZATEK                          #
                    ###########################################################
                    #       z ksiazki od Kacpra, wzory 19.14, str. 405        #
                    ###########################################################
                    
#                    # wektory w kierunku normalnym zderzenia: v1n, v2n
#                    v11 = Point(FirstBall.getVelocity().getVx(), FirstBall.getVelocity().getVy())
#                    v1n = Velocity(SecondBall.getPosition().getPositionX()-FirstBall.getPosition().getPositionX(), SecondBall.getPosition().getPositionY()-FirstBall.getPosition().getPositionY())
#                    v1n.multiplyVector(1.0/v1n.getVectorLength())
#                    v1nLength = abs(v11.getDistanceToLine(Point(0,0), Point(v1n.getVx(), v1n.getVy())))
#                    v1n.multiplyVector(v1nLength)
#                    #
#                    v21 = Point(SecondBall.getVelocity().getVx(), SecondBall.getVelocity().getVy())
#                    v2n = Velocity(FirstBall.getPosition().getPositionX()-SecondBall.getPosition().getPositionX(), FirstBall.getPosition().getPositionY()-SecondBall.getPosition().getPositionY())
#                    v2n.multiplyVector(1.0/v2n.getVectorLength())
#                    v2nLength = abs(v21.getDistanceToLine(Point(0,0), Point(v2n.getVx(), v2n.getVy())))
#                    v2n.multiplyVector(v2nLength)
#
#                    
#                    n = deepcopy(v2n)
#                    n.multiplyVector(1.0/n.getVectorLength())
#                    
#                    
#                    deltavn = v1n.getVectorLength() - v2n.getVectorLength()
#                    mi = 1.0 / (1.0/m1 + 1.0/m2)
#                    J = -mi * (k+1) * deltavn
#                    
#                    J1n = deepcopy(n)
#                    J1n.multiplyVector(J/m1)
#                    W1 = deepcopy(v1)
#                    W1.addVector(J1n)
#                    
#                    J2n = deepcopy(n)
#                    J2n.multiplyVector(J/m2)
#                    W2 = deepcopy(v2)
#                    W2.substractVector(J2n)
                    
                    ###########################################################
                    #                       SPOSOB 2                          #
                    #                        KONIEC                           #
                    ###########################################################
                    
                    ###########################################################
                    #                       SPOSOB 3                          #
                    #                       POCZATEK                          #
                    ###########################################################
                    #  z ksiazki od Kacpra, wzory 19.14 z modyfik., str. 405  #
                    ###########################################################
                    
                    # wektory w kierunku normalnym zderzenia: v1n, v2n
                    #v11 = Point(FirstBall.getVelocity().getVx(), FirstBall.getVelocity().getVy())
                    v1n = Velocity(SecondBall.getPosition().getPositionX()-FirstBall.getPosition().getPositionX(), SecondBall.getPosition().getPositionY()-FirstBall.getPosition().getPositionY())
                    #v1n.multiplyVector(1.0/v1n.getVectorLength())
                    #v1nLength = abs(v11.getDistanceToLine(Point(0,0), Point(v1n.getVx(), v1n.getVy())))
                    #v1n.multiplyVector(v1nLength)
                    #
                    #v21 = Point(SecondBall.getVelocity().getVx(), SecondBall.getVelocity().getVy())
                    v2n = Velocity(FirstBall.getPosition().getPositionX()-SecondBall.getPosition().getPositionX(), FirstBall.getPosition().getPositionY()-SecondBall.getPosition().getPositionY())
                    #v2n.multiplyVector(1.0/v2n.getVectorLength())
                    #v2nLength = abs(v21.getDistanceToLine(Point(0,0), Point(v2n.getVx(), v2n.getVy())))
                    #v2n.multiplyVector(v2nLength)
                    
                    
                    n1 = deepcopy(v2n)
                    n = n1.normalize()
                    
                    
                    deltavn = deepcopy(v1n)
                    deltavn.substractVector(v2n)
                    mi = 1.0 / (1.0/m1 + 1.0/m2)
                    J = deepcopy(deltavn)
                    J.multiplyVector(-mi * (k+1))
                    
                    J1n = deepcopy(J)
                    J1n.multiplyVector(1.0/m1)
                    J1n.dot(n)
                    W1 = deepcopy(v1)
                    W1.addVector(J1n)
                    
                    J2n = deepcopy(J)
                    J2n.multiplyVector(1.0/m2)
                    J2n.dot(n)
                    W2 = deepcopy(v2)
                    W2.substractVector(J2n)
                    
                    ###########################################################
                    #                       SPOSOB 3                          #
                    #                        KONIEC                           #
                    ###########################################################
                    
                    ###########################################################
                    #                       SPOSOB 4                          #
                    #                                                         #
                    ###########################################################
                    
                    
                    # dodanie nowoobliczonych wektorow do newBallVelocities
                    
#                    # wektory w kierunku normalnym zderzenia: v1n, v2n
#                    v11 = Point(FirstBall.getVelocity().getVx(), FirstBall.getVelocity().getVy())
#                    v1n = Velocity(SecondBall.getPosition().getPositionX()-FirstBall.getPosition().getPositionX(), SecondBall.getPosition().getPositionY()-FirstBall.getPosition().getPositionY())
#                    v1n.multiplyVector(1.0/v1n.getVectorLength())
#                    v1nLength = abs(v11.getDistanceToLine(Point(0,0), Point(v1n.getVx(), v1n.getVy())))
#                    v1n.multiplyVector(v1nLength)
#                    #
#                    v21 = Point(SecondBall.getVelocity().getVx(), SecondBall.getVelocity().getVy())
#                    v2n = Velocity(FirstBall.getPosition().getPositionX()-SecondBall.getPosition().getPositionX(), FirstBall.getPosition().getPositionY()-SecondBall.getPosition().getPositionY())
#                    v2n.multiplyVector(1.0/v2n.getVectorLength())
#                    v2nLength = abs(v21.getDistanceToLine(Point(0,0), Point(v2n.getVx(), v2n.getVy())))
#                    v2n.multiplyVector(v2nLength)
                    

                    
#                    n1axis = Vector(FirstBall.getPosition().getPositionX() - SecondBall.getPosition().getPositionX(), FirstBall.getPosition().getPositionY() - SecondBall.getPosition().getPositionY())
#                    n2axis = Vector(SecondBall.getPosition().getPositionX() - FirstBall.getPosition().getPositionX(), SecondBall.getPosition().getPositionY() - FirstBall.getPosition().getPositionY())
#                    nn1axis = n1axis.normalize()
#                    nn2axis = n2axis.normalize()
#                    
#                    v1angx = v1.getVectorAngle()
#                    nn1axisang = nn1axis.getVectorAngle()
#                    v2angx = v2.getVectorAngle()
#                    nn2axisang = nn2axis.getVectorAngle() 
#                    
#                    v1ang = abs(v1angx - nn1axisang)
#                    v2ang = abs(v2angx - nn2axisang)
                    
                   #v1ang = v1.calculateAngleBetweenVector(nnaxis)
                   #v2ang = v2.calculateAngleBetweenVector(nnaxis)
#                    
#                    v1len = v1.getVectorLength()
#                    v2len = v2.getVectorLength()
                    
#                    v1n = v1len * cos(v1ang)
#                    v2n = v2len * cos(v2ang)
#                    v1t = v1len * sin(v1ang)
#                    v2t = v2len * sin(v2ang)
                    
                    # wektory w kierunku stycznym zderzenia: v1t, v2t
                    
                    # wektory w kierunku normalnym po zderzeniu: W1n, W2n
#                    a = (m1 - m2*k)/(m1+m2)
#                    b = (m2 * (k+1))/(m1+m2)
#                    v1na = v1n * a
#                    v2nb = v2n * b
#                    W1n = v1na + v2nb
#                    v1nb = v1n * b
#                    v2na = v2n * a
#                    W2n = v1nb + v2na
                    
                    # wektory po zderzeniu: W1, W2
#                    W1 = deepcopy(v1)
#                    W1.setVx(W1n)
#                    W1.setVy(v1t)
#                    W2 = deepcopy(v2)
#                    W2.setVx(W2n)
#                    W2.setVy(v2t)
                    
                    ###########################################################
                    #                       SPOSOB 4                          #
                    #                       koniec                            #
                    ###########################################################
                    
                    ###########################################################
                    #                       SPOSOB 5                          #
                    #                       Początek                          #
                    ###########################################################
                 
                    
#                    # wektory w kierunku normalnym zderzenia: v1n, v2n
#                    naxis = Vector(FirstBall.getPosition().getPositionX() - SecondBall.getPosition().getPositionX(), FirstBall.getPosition().getPositionY() - SecondBall.getPosition().getPositionY())
#                    nnaxis = naxis.normalize()
#                    
#                    a = naxis.dot(v1)
#                    b = naxis.dot(v2)
#                    V1x = naxis.getVx()*a
#                    c = deepcopy(v1)
#                    c.setVx(V1x)
#                    c.setVy(0)
#                    V1y = v1-c
#                    V2x = naxis.getVx()*b
#                    u = deepcopy(v2)
#                    u.setVx(V2x)
#                    u.setVy(0)
#                    V2y = v2-u
#                    
#                    W1x = ((V1x*m1)+(V2x*m2)-(V1x-V2x)*m2)/(m1+m2)
#                    W2x = ((V1x*m1)+(V2x*m2)-(V2x-V1x)*m1)/(m1+m2)
#                    W1 = deepcopy(v1) 
#                    W1.setVx(W1x)
#                    W1.setVy(V1y)
#                    W2 = deepcopy(v2)
#                    W2.setVx(W2x)
#                    W2.setVy(V2y)
                    
                    ###########################################################
                    #                       SPOSOB 4                          #
                    #                       koniec                            #
                    ###########################################################
                    
                    
                    # wektory w kierunku normalnym zderzenia: v1n, v2n
                    
                    #v1n = Velocity(SecondBall.getPosition().getPositionX()-FirstBall.getPosition().getPositionX(), SecondBall.getPosition().getPositionY()-FirstBall.getPosition().getPositionY())
                    
                    #v2n = Velocity(FirstBall.getPosition().getPositionX()-SecondBall.getPosition().getPositionX(), FirstBall.getPosition().getPositionY()-SecondBall.getPosition().getPositionY())
                  
                    #naxis = Vector(FirstBall.getPosition().getPositionX() - SecondBall.getPosition().getPositionX(), FirstBall.getPosition().getPositionY() - SecondBall.getPosition().getPositionY())
                    #nnaxis = naxis.normalize()
                    
                    
                    #v1nl = v1n.getVectorLength()
                    #v2nl = v2n.getVectorLength()
                    
                                        
                    #deltavn = v1nl - v2nl
                    #mi = 1.0 / (1.0/m1 + 1.0/m2)
                    #J = (-mi * (k+1)) * deltavn
                    #a = (J / m1) * (nnaxis)
                    #W1 = v1 + a
                    #b = (J / m2) * (nnaxis)
                    #W2 = v2 - b
                    
                    ###########################################################
                    #                       SPOSOB 3                          #
                    #                        KONIEC                           #
                    ###########################################################
                    
                    # przesuniecie pileczek jesli na siebie nachodzily
                    #if BallsAreIntersecting:
                    #    v1s12 = deepcopy(v1s1)
                    #    v1s12.multiplyVector(0.5)
                    #    FirstBall.getPosition().moveAgainstVector(v1s12)
                    #    v2s22 = deepcopy(v2s2)
                    #    v2s22.multiplyVector(0.5)
                    #    SecondBall.getPosition().moveAgainstVector(v2s22)
                    
                    # dodanie nowoobliczonych wektorow do newBallVelocities
                    newBallVelocities[i].append(W1)
                    newBallVelocities[j].append(W2)
        
        
        for i in range(0, len(self.BallsList)):
            if len(newBallVelocities[i]) > 0:
                newVelocity = Vector(0,0)
                
                for velocity in newBallVelocities[i]:
                    newVelocity.addVector(velocity)
                
                self.BallsList[i].setVelocity(newVelocity)
        
    
    
    def calculateBallPositionAfterTime(self, ball, t):
        """
        Funkcja obliczająca położenie piłeczki po czasie t, w którym znalazłaby
        się piłeczka, jeśli nie nastąpiłaby w tym czasie kolizja.
        
        Jeżeli grawitacja jest włączona następuje przeliczanie wypadkowych
        sił grawitacji. Jeżeli opcja jest wyłączona wypadkowe siły grawitacji
        są równe zeru.        
        
        @param ball: Parametr określający piłeczkę.
        @param t: Parametr określający czas.
                      
        @return: Funkcja zwraca nowe położenie piłeczki po upływie czasu.
        """
        
        # poprzednie wartości prędkości i położenia piłeczki
        v0 = ball.getVelocity()
        v0x = v0.getVx()
        v0y = v0.getVy()
        
        s = ball.getPosition()
        sx = s.getPositionX()
        sy = s.getPositionY()
        
        # wartości składowych przyspieszenia ziemskiego
        if Options.GravityOn:
            g = Gravity(Options.GravityAngle, Options.GravitationalAcceleration)
            gx = g.getVx()
            gy = g.getVy()
        else:
            gx = 0
            gy = 0
        
        # obliczenie nowego położenia piłeczki
        NewSx = sx + (v0x * t) + (gx * t*t)/2
        NewSy = sy + (v0y * t) + (gy * t*t)/2
        
        return Point(NewSx, NewSy)
    
    
    def calculateBallVelocityAfterTime(self, ball, t):
        """
        Funkcja obliczająca położenie piłeczki po czasie t, w którym znalazłaby
        się piłeczka, jeśli nie nastąpiłaby w tym czasie kolizja.
        """
        
        # poprzednie wartości prędkości piłeczki
        v0 = ball.getVelocity()
        v0x = v0.getVx()
        v0y = v0.getVy()
        
        # wartości składowych przyspieszenia ziemskiego
        if Options.GravityOn:
            g = Gravity(Options.GravityAngle, Options.GravitationalAcceleration)
            gx = g.getVx()
            gy = g.getVy()
        else:
            gx = 0
            gy = 0
        
        # obliczenie nowej prędkości piłeczki
        v0x = v0x + (gx * t)
        v0y = v0y + (gy * t)
        
        return Velocity(v0x, v0y)
    
    
    def simulate(self, iterationStartTime=0):
        """
        Procedura obsługująca główną pętlę symulacji.
        
        Procedura ta wykrywa kolizje piłeczek z innymi obiektami, wylicza ich
        nowe położenia i prędkości wynikłe z kolizji i odświeża płótno. Po
        upłynięciu czasu równego dt = 1/Options.FramesPerSecond procedura
        uruchamia się ponownie z czasem symulacji zwiększonym o dt.
        
        Reaguje na naciśnięcie przycisków paska narzędzi.
        
        @param iterationStartTime: Czas rozpoczęcia kolejnej pętli symulacji
        w sekundach. Jeśli nie podano, to domyślnie przyjmuje wartość 0.
        """
        
        # ustawienie precyzji obliczeń
        epsilon = 1e-10
        
        
        # przyrost czasu na jedną iterację
        dt = 1 / Options.FramesPerSecond
        
        
        # (1)
        # czas następnego wyrysowania piłeczek (T) o ile nie wykryje
        # się w tym kroku kolizji
        T = iterationStartTime + dt
        
        
        lastRefreshTime = iterationStartTime
        
        
        
        if self.PlaySimulation and not self.PauseSimulation:
            while lastRefreshTime < T:
            
                nextRefreshTime = T
                timeRemaining = T - lastRefreshTime
                
                
                # (2)
                # obliczenie czasu w którym jakaś piłeczka będzie miała pierwszą
                # kolizję, z czym (inna piłeczka, ściana) i kiedy (nextRefreshTime)
            
                
                for i in range(0, len(self.BallsList)):
                    ball = self.BallsList[i]
                    newBallPosition = self.calculateBallPositionAfterTime(ball, timeRemaining)
                    
                    Oa = ball.getPosition()
                    Oax = Oa.getPositionX()
                    Oay = Oa.getPositionY()
                    Dax = newBallPosition.getPositionX() - Oax
                    Day = newBallPosition.getPositionY() - Oay
                    
                    #x01 = ball.getPosition().getPositionX()
                    #y01 = ball.getPosition().getPositionY()
                    #V1 = ball.getVelocity()
                    #V1Length = V1.getVectorLength()
                    #theta1 = V1.getVectorAngle()
                    #r1 = ball.getRadius()
                    
                    
                    # kolizje z innymi piłeczkami
                    for j in range(i+1, len(self.BallsList)):
                        anotherBall = self.BallsList[j]
                        
                        #x02 = anotherBall.getPosition().getPositionX()
                        #y02 = anotherBall.getPosition().getPositionY()
                        #V2 = anotherBall.getVelocity()
                        #V2Length = V2.getVectorLength()
                        #theta2 = V2.getVectorAngle()
                        #r2 = anotherBall.getRadius()
                        #
                        ## cosinus roznicy katow theta1-theta2
                        #cos_theta1_theta2 = cos(theta1) * cos(theta2) + sin(theta1) * sin(theta2)
                        #
                        ## wspolczynniki rownania kwadratowego dla obliczenia czasu zderzenia
                        #a = V1Length**2 + V2Length**2 - 2*V1Length*V2Length*cos_theta1_theta2
                        #b = -2 * ((x01+x02)*(V1Length*cos(theta1)+V2Length*cos(theta2)) + (y01+y02)*(V1Length*sin(theta1)+V2Length*sin(theta2)))
                        #c = (x01-x02)**2 + (y01-y02)**2 - (r1+r2)**2
                        # 
                        ## wyznacznik rownania kwadratowego
                        #delta = b**2 - 4*a*c
                        #
                        ## czas zderzenia
                        #
                        #tzd = nextRefreshTime+100
                        #
                        #if delta>0 and a!=0:
                        #    t1zd = (-b - sqrt(delta))/2*a
                        #    t2zd = (-b + sqrt(delta))/2*a
                        #    
                        #    print("t1zd = {0}".format(t1zd))
                        #    print("t2zd = {0}".format(t2zd))
                        #    
                        #    
                        #    if (t1zd==0 and t2zd>0) or (t2zd==0 and t1zd>0):
                        #        tzd = lastRefreshTime
                        #    elif t1zd>0 and t2zd>0 and t1zd<=dt and t2zd<=dt:
                        #        tzd = lastRefreshTime + min(t1zd, t2zd)
                        #        
                        #    print("nextRefreshTime+100 = {0}".format(nextRefreshTime+100))
                        #    print("tzd = {0}".format(tzd))
                        #    
                        #    # wyczyszczenie listy kolidujacych pileczek
                        #    # jesli znaleziono wczesniejszy czas zderzenia
                        #    if tzd<nextRefreshTime:
                        #        for k in range(0, len(collidingBalls)):
                        #            collidingBalls[k] = 0
                        #    
                        #    # dodanie aktualnie sprawdzanych pileczek do
                        #    # listy kolidujacych
                        #    if tzd<=nextRefreshTime:
                        #        collidingBalls[i] = 1
                        #        collidingBalls[j] = 1
                        #        nextRefreshTime = tzd
                                    
                        
                        newAnotherBallPosition = self.calculateBallPositionAfterTime(anotherBall, timeRemaining)
                        Ob = anotherBall.getPosition()
                        Obx = Ob.getPositionX()
                        Oby = Ob.getPositionY()
                        Dbx = newAnotherBallPosition.getPositionX() - Obx
                        Dby = newAnotherBallPosition.getPositionY() - Oby
                        
                        R = (ball.getRadius() + anotherBall.getRadius())**2
                        Ox = Oax-Obx
                        Dx = Dax-Dbx
                        Oy = Oay-Oby
                        Dy = Day-Dby
                        
                        a = Dx**2 + Dy**2
                        b = 2*Ox*Dx + 2*Oy*Dy
                        c = Ox**2 + Oy**2 - R
                        delta = b**2 - 4*a*c
                        
                        
                        if delta >= 0 and a!=0:
                            
                            if delta==0:
                                t = -b/(2*a)
                                
                                collisionTime = lastRefreshTime + (timeRemaining)*t
                                if 0<t and t<1 and collisionTime<nextRefreshTime and collisionTime-lastRefreshTime>epsilon:
                                    nextRefreshTime = collisionTime
                            
                            else:
                                t1 = (-b-sqrt(delta))/(2*a)
                                t2 = (-b+sqrt(delta))/(2*a)
                                
                                collisionTime = lastRefreshTime + (timeRemaining)*t1
                                if 0<t1 and t1<1 and collisionTime<nextRefreshTime and collisionTime-lastRefreshTime>epsilon:
                                    nextRefreshTime = collisionTime
                                
                                collisionTime = lastRefreshTime + (timeRemaining)*t2
                                if 0<t2 and t2<1 and collisionTime<nextRefreshTime and collisionTime-lastRefreshTime>epsilon:
                                    nextRefreshTime = collisionTime
                    
                    
                    # kolizje ze ścianami
                    # źródło: http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
                    # źródło: http://seb.ly/2010/01/predicting-circle-line-collisions/
                    for wall in self.WallsList:
                        
                        # punkty wyznaczające granice ściany
                        a = wall.getPoint1()
                        b = wall.getPoint2()
                        
                        # odległość między środkiem piłeczki a ścianą na początku ruchu
                        d1 = ball.getPosition().getDistanceToLine(a, b)
                        
                        # odległość między środkiem piłeczki a ścianą na końcu ruchu
                        d2 = newBallPosition.getDistanceToLine(a, b)
                        
                        if d1<d2:
                            d1 = -d1
                            d2 = -d2
                        
                        
                        if (d2-d1)!=0:
                            t = (ball.getRadius()-d1) / (d2-d1)
                            
                            if 0<t and t<1:
                                collisionTime = lastRefreshTime + (timeRemaining)*t
                                
                                if collisionTime<nextRefreshTime and collisionTime-lastRefreshTime>epsilon:
                                #if nextRefreshTime-collisionTime>epsilon and abs(collisionTime-lastRefreshTime)>epsilon:
                                    #print("ball.getRadius() = {0}".format(ball.getRadius()))
                                    #print("d1 = {0}".format(d1))
                                    #print("d2 = {0}".format(d2))
                                    #print("lastRefreshTime = {0}".format(lastRefreshTime))
                                    #print("collisionTime = {0}".format(collisionTime))
                                    #print("nextRefreshTime = {0}".format(nextRefreshTime))
                                    
                                        
                                    nextRefreshTime = collisionTime
                        
                        
                        
                
                # jeśli (possibleCollisionTime) = (T), to  w tym
                # kroku albo nie będzie kolizji albo zdarzy się ona
                # dokładnie w czasie (T)
                # jeśli (possibleCollisionTime) < (T), to w tym kroku będzie
                # co najmniej jedna kolizja
                
                # (3)
                # przesunięcie wszystkich piłeczek na ekranie do czasu
                # (possibleCollisionTime)
                
                timeElapsed = nextRefreshTime - lastRefreshTime
                
                
                print("timeElapsed = {0}".format(timeElapsed))
                print("-----")
                
                
                for ball in self.BallsList:
                    NewPosition = self.calculateBallPositionAfterTime(ball, timeElapsed)
                    NewVelocity = self.calculateBallVelocityAfterTime(ball, timeElapsed)
                    
                    # aktualizacja prędkości i położenia piłeczki                    
                    ball.setVelocity(NewVelocity)
                    ball.setPosition(NewPosition)
                
                
                # (4)
                # zaktualizowanie wektorów, jeśli jakieś piłeczki z czymś
                # kolidują
                
                # odbicie piłeczek od ścian
                self.bounceBallsAgainstCollidingWalls()
                
                # odbicie piłeczek od siebie
                self.calculateBallsVelocitiesAfterCollision()
                #for i in range(0, len(self.BallsList)):
                #    ball = self.BallsList[i]
                #    
                #    for j in range(i+1, len(self.BallsList)):
                #        anotherBall = self.BallsList[j]
                #        
                #        if ball.collidesWith(anotherBall):
                #            ball.resolveCollision(anotherBall)
                
                
                # zaokraglenie predkosci
                for ball in self.BallsList:
                    if abs(ball.getVelocity().getVx()) < epsilon:
                        ball.getVelocity().setVx(0)
                    if abs(ball.getVelocity().getVy()) < epsilon:
                        ball.getVelocity().setVy(0)
                
                
                # zaktualizowanie czasu symulacji na ekranie
                self.SimulationTime = nextRefreshTime
                            
                
                # (5)
                # zaktualizowanie zegara i odświeżenie ekranu
                lastRefreshTime = nextRefreshTime
                self.Refresh(False)
                self.Update()
                
        
        
        # (6)
        # jeśli czas jest mniejszy od (T), to powtórz od (2)
        
        self.Refresh(False)
        self.Update()
                
        # ustawienie następnego uruchomienia procedury dla następnej iteracji
        if self.PlaySimulation and not self.StopSimulation:
            if self.PauseSimulation:
                Timer(dt, self.simulate, [iterationStartTime]).start()
            else:
                Timer(dt, self.simulate, [iterationStartTime+dt]).start()



class OptionsDialog(wx.Dialog):
    """
    Klasa okna dialogowego z wyborem opcji symulacji.
    
    @param wx.Dialog: Klasa okna dialogowego, po którym dziedziczy okno opcji.
    """
    
    def __init__(self):
        """
        Konstruktor tworzący okno dialogowe i pobierający uchwyty do
        poszczególnych przycisków, a także ustawiający im wartości początkowe
        pobrane z atrybutów klasy Options.
        """
        
        # pobranie uchwytów do elementów okna
        self.ApplyButton = xrc.XRCCTRL(self, "ApplyButton")
        self.GravityOnCheckbox = xrc.XRCCTRL(self, "GravityOnCheckbox")        
        self.GravityAngleTextCtrl = xrc.XRCCTRL(self, "GravityAngleValue")
        self.CoefficientOfRestitutionTextCtrl = xrc.XRCCTRL(self, "CoefficientOfRestitutionValue")
        self.BallVelocityMinSlider = xrc.XRCCTRL(self, "BallVelocityMinValue")
        self.BallVelocityMaxSlider = xrc.XRCCTRL(self, "BallVelocityMaxValue")
        self.BallRadiusMinSlider = xrc.XRCCTRL(self, "BallRadiusMinValue")
        self.BallRadiusMaxSlider = xrc.XRCCTRL(self, "BallRadiusMaxValue")
        self.BallMassMinSlider = xrc.XRCCTRL(self, "BallMassMinValue")
        self.BallMassMaxSlider = xrc.XRCCTRL(self, "BallMassMaxValue")
        self.NumberOfBallsSlider = xrc.XRCCTRL(self, "NumberOfBallsValue")
        
        # ustawienie wartości suwaków ostatnio zapisanymi wartościami w klasie
        # Options
        self.GravityOnCheckbox.SetValue(Options.GravityOn)
        self.GravityAngleTextCtrl.SetValue(str(Options.GravityAngle))
        self.CoefficientOfRestitutionTextCtrl.SetValue(str(Options.CoefficientOfRestitution))
        self.BallVelocityMinSlider.SetValue(Options.BallVelocityMin)
        self.BallVelocityMaxSlider.SetValue(Options.BallVelocityMax)
        self.BallRadiusMinSlider.SetValue(Options.BallRadiusMin)
        self.BallRadiusMaxSlider.SetValue(Options.BallRadiusMax)
        self.BallMassMinSlider.SetValue(Options.BallMassMin)
        self.BallMassMaxSlider.SetValue(Options.BallMassMax)
        self.NumberOfBallsSlider.SetValue(Options.NumberOfBalls)
    
    
    def OnResetToDefaultsClick(self, Event):
        """
        Procedura ustawiająca domyślne wartości suwaków opcji symulacji.
        
        @param Event: Parametr określający obsługiwane zdarzenie.
        """
        
        self.GravityOnCheckbox.SetValue(Options.DefaultGravityOn)
        self.GravityAngleTextCtrl.SetValue(str(Options.DefaultGravityAngle))
        self.CoefficientOfRestitutionTextCtrl.SetValue(str(Options.DefaultCoefficientOfRestitution))
        self.BallVelocityMinSlider.SetValue(Options.DefaultBallVelocityMin)
        self.BallVelocityMaxSlider.SetValue(Options.DefaultBallVelocityMax)
        self.BallRadiusMinSlider.SetValue(Options.DefaultBallRadiusMin)
        self.BallRadiusMaxSlider.SetValue(Options.DefaultBallRadiusMax)
        self.BallMassMinSlider.SetValue(Options.DefaultBallMassMin)
        self.BallMassMaxSlider.SetValue(Options.DefaultBallMassMax)
        self.NumberOfBallsSlider.SetValue(Options.DefaultNumberOfBalls)
    
    
    def OnApplyClick(self, Event):
        """
        Procedura zatwierdzająca zmiany wprowadzone w opcjach symulacji.
                       
            1. Walidacja wpisanej liczby w pole GravityAngle:
                - Funkcja sprawdza czy wpisana wartość kąta grawitacji jest
                liczbą całkowitą.
                - Jeżeli wartość nie jest liczbą całkowitą to do listy błędów
                wpisywany jest wyjątek: 
                "Kąt grawitacji musi być liczbą całkowitą."
                
            2. Walidacja wpisanej liczby w pole CoefficientOfRestitution:
                - Funkcja sprawdza czy wpisana wartość współczynnika
                sprężystości jest liczbą mieszczącą się w przedziale [0.0, 1.0].
                - Jeżeli wartość nie jest liczbą mieszczącą się w przedziale
                [0.0, 1.0] to do listy błędów wpisywany jest wyjątek:
                "Współczynnik sprężystości musi być liczbą z przedziału
                [0.0, 1.0]."
                
            3. Walidacja czy wartości Min są mniejsze lub równe wartościom Max
                - Funkcja sprawdza czy wpisane wartości minimalne dla 
                parametrów:
                    - Promień.
                    - Prędkość.
                    - Masa.
                Są mniejsze lub równe wartościom maksymalnym.
                - Jeżeli któraś z wartości Min jest większa od wartości Max to
                to do listy błędów wpisywany jest jeden lub więcej wyjąteków: 
                "Minimalny/a <Parametr> nie może być większy/a od maksymalnej."
            
            4. Jeżeli lista błędów jest pusta następuje zapisanie ustawionych
               w oknie opcji parametrów symulacji. Natomiast w przypadku gdy
               W liście błędó znajdują się wpisane wyjątki następuje ich
               wyświetlenie w nowym oknie.
                
        @param Event: Parametr określający obsługiwane zdarzenie.        
        """
        
        ValidationErrors = []

        
        # walidacja wpisanej liczby w pole GravityAngle
        try:
            int(self.GravityAngleTextCtrl.GetValue())
        except ValueError:
            ValidationErrors.append("Kąt grawitacji musi być liczbą całkowitą.")
        
        
        # walidacja wpisanej liczby w pole CoefficientOfRestitution
        try:
            float(self.CoefficientOfRestitutionTextCtrl.GetValue())
            
            if float(self.CoefficientOfRestitutionTextCtrl.GetValue()) > 1.0 or float(self.CoefficientOfRestitutionTextCtrl.GetValue()) < 0.0:
                ValidationErrors.append("Współczynnik sprężystości musi być liczbą z przedziału [0.0, 1.0].")
        except ValueError:
            ValidationErrors.append("Współczynnik sprężystości musi być liczbą z przedziału [0.0, 1.0].")

        
        # walidacja czy wartości Min są na pewno <= wartościom Max
        if self.BallVelocityMinSlider.GetValue() > self.BallVelocityMaxSlider.GetValue():
            ValidationErrors.append("Minimalna prędkość nie może być większa od maksymalnej.")
        
        if self.BallRadiusMinSlider.GetValue() > self.BallRadiusMaxSlider.GetValue():
            ValidationErrors.append("Minimalny promień nie może być większy od maksymalnego.")
        
        if self.BallMassMinSlider.GetValue() > self.BallMassMaxSlider.GetValue():
            ValidationErrors.append("Minimalna masa nie może być większa od maksymalnej.")
        
        
        # jeśli wszystko poszło ok, to nadpisujemy poprzednie wartości
        if len(ValidationErrors) == 0:
            Options.GravityOn = self.GravityOnCheckbox.GetValue()
            Options.GravityAngle = int(self.GravityAngleTextCtrl.GetValue())
            Options.CoefficientOfRestitution = float(self.CoefficientOfRestitutionTextCtrl.GetValue())
            Options.BallVelocityMin = self.BallVelocityMinSlider.GetValue()
            Options.BallVelocityMax = self.BallVelocityMaxSlider.GetValue()
            Options.BallRadiusMin = self.BallRadiusMinSlider.GetValue()
            Options.BallRadiusMax = self.BallRadiusMaxSlider.GetValue()
            Options.BallMassMin = self.BallMassMinSlider.GetValue()
            Options.BallMassMax = self.BallMassMaxSlider.GetValue()
            Options.NumberOfBalls = self.NumberOfBallsSlider.GetValue()
            
            self.Destroy()
            
        # jeśli są jakieś błędy, to wyświetlamy odpowiedni komunikat
        else:
            ErrorMessage = ""
            
            for i in range(0, len(ValidationErrors)):
                ErrorMessage = ErrorMessage + "\n" + str(i+1) + ". " + ValidationErrors[i]
            
            dlg = wx.MessageDialog(self, "Wystąpiły błędy walidacji:"+ErrorMessage, "Błędy walidacji", wx.OK | wx.ICON_WARNING)
            dlg.ShowModal()
            dlg.Destroy()



class PythonDrawApp(wx.App):
    """
    Główna klasa aplikacji.
    
    @param wx.App: Klasa, po której dziedziczy główna klasa aplikacji.
    """
    
    
    def OnInit(self):
        """
        Wczytuje główną formatkę aplikacji i ustala dla niej obslugę zdarzeń.
        
        @return: B{True} - jeżeli inicjalizacja głównych okien i obiektów
        programu przebieła pomyślnie.
        """
        
        self.Res = xrc.XmlResource("wxGUI.xrc")
        
        self.MainFrame = self.Res.LoadFrame(None, "MainFrame")
        self.MainFrame.__class__ = MainFrame
        
        application_icon = wx.Icon("Data\icon_main.png", wx.BITMAP_TYPE_PNG)
        self.MainFrame.SetIcon(application_icon)
        
        self.MainFrame.Bind(wx.EVT_MENU, self.MainFrame.OnMenuFileExitClick, id = xrc.XRCID("MenuFileExit"))
        self.MainFrame.Bind(wx.EVT_TOOL, self.MainFrame.OnMenuFileExitClick, id = xrc.XRCID("ToolExit"))
        self.MainFrame.Bind(wx.EVT_CLOSE, self.MainFrame.OnMenuFileExitClick)

        
        # pobiera identyfikator paska narzędzi
        self.ToolBar = xrc.XRCCTRL(self.MainFrame, "ToolBar")
        
        
        # Pobiera identyfikator plotna i ustala dla niego obsluge zdarzen
        self.Canvas = xrc.XRCCTRL(self.MainFrame, "Canvas")
        self.Canvas.__class__ = Canvas
        self.Canvas.__init__(self.ToolBar, self.MainFrame)
        
        self.Canvas.Bind(wx.EVT_PAINT, self.Canvas.OnPaint, id = xrc.XRCID("Canvas"))
        self.Canvas.Bind(wx.EVT_SIZE, self.Canvas.OnSize, id = xrc.XRCID("Canvas"))
        
        self.ToolBar.Bind(wx.EVT_TOOL, self.Canvas.OnTool, id = xrc.XRCID("ToolStop"))
        self.ToolBar.Bind(wx.EVT_TOOL, self.Canvas.OnTool, id = xrc.XRCID("ToolPlay"))
        self.ToolBar.Bind(wx.EVT_TOOL, self.Canvas.OnTool, id = xrc.XRCID("ToolPause"))
        self.ToolBar.Bind(wx.EVT_TOOL, self.Canvas.OnTool, id = xrc.XRCID("ToolVelocities"))
        self.ToolBar.Bind(wx.EVT_TOOL, self.Canvas.OnOptions, id = xrc.XRCID("ToolOptions"))

        
        # Pobiera identyfikator paska stanu
        self.StatusBar = xrc.XRCCTRL(self.MainFrame, "StatusBar")
        
        
        self.MainFrame.Show()
        
        #system("cls")
        
        return True



if __name__ == "__main__":

    # pozbywa się warninga "iCCP: known incorrect sRGB profile"
    wx.Log.SetLogLevel(0)
    
    system("cls")
    
    App = PythonDrawApp(False)
    App.MainLoop()